/***************************************************************************
 *   Copyright (C) 2007 by Grzegorz Latarowski, Maciej Gajewski            *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <QDebug>


// stdlib
#include <sys/time.h>   // hi-res clock (gettimeofday)

// Qt
#include <QSqlQuery>
#include <QSqlDatabase>

// gnet
#include "gnetpackets.h"

// autonet
#include "autopackets.h"

// camera
#include "camera.h"

// local
#include "camera2params.h"
#include "globaldata.h"
#include "constants.h"
#include "eventsbaseconnection.h"
#include "eventserver.h"



// --------------------- EventServer::run() ----------------------------------
void EventServer::run()
{
	qDebug("Starting Event server: %s %s", __DATE__, __TIME__ );

		// register packets
	GAutoPacket::registerPackets();

	init();
	
	_image_id = 0;

	
		// timeouts for jpeg sending
	uint64_t startClock = camera_get_time();
	uint64_t endClock   = 0;
	quint16 msecs = 0;
	
		// udp stream params
	QHostAddress targetAddress;
	quint16 targetPort = 0;
	
	// ====================================================
	// main thread loop
	while( 1 )
	{
		// ------------------------------------------------
		// check for new configuration
		
			// load new params if there are new ones
		if ( GlobalData::instance()->paramsNew() )
		{
			readParams( GlobalData::instance()->getParams() );
		}
			// check if there is new udp receiver waiting for jpegs
		if ( GlobalData::instance()->addressNew() )
		{
			GlobalData::instance()->acknowledgeNewAddress();
			targetAddress = GlobalData::instance()->getUdpAddress();
			targetPort = GlobalData::instance()->getUdpPort();
		}

		
		// ------------------------------------------------
		// read jpeg image
		
		uint64_t jpegTimestamp = 0;
		QByteArray jpegImage = readJpeg( &jpegTimestamp );

		
		// ------------------------------------------------
		// add image to buffer
		
		addImageToBuffer( jpegImage, jpegTimestamp );


		// ------------------------------------------------
		// stream image via udp if there is some receiver
		// waiting and sending timeout occures 

		if ( ! targetAddress.isNull() && targetPort > 0 )
		{
				// check if timedout
			endClock = camera_get_time();
			msecs = endClock - startClock;

				// timedout - send jpeg to host 
			if( ( msecs > _jpegSendingInterval ) && ( _jpegSendingInterval > 0 ) )
			{
				sendJpeg( jpegImage, targetAddress, targetPort );
					// clear timer
				startClock = camera_get_time();
			}
		}


		// ------------------------------------------------
		// check for trigger packet, if there is one
		// save event to database
		
		_udpReceiver.receiveDatagrams();

		usleep( 30 );
		
	} // !while( 1 )
}
// ---------------------------------------------------------------------------


// ---------------------- EventServer::init() --------------------------------
void EventServer::init()
{
		// init camera device
	if ( CAMERA_ERROR == camera_init() )
	{
		QString msg = QString( "Error initializing camera: %1" )
				.arg( camera_error_message() );
		throw GConfigurationError( msg );
	}
		// trun off auto exposure, set 60ms exposure time
	camera_set_auto_exposure( 0 );
	camera_set_exposure( 60 );

	
		// load params
	GParams params;
	try
	{
		params.loadFromFile( PARAMS_CONFIG_FILE );
	}
	catch( const GException & e )
	{
		qWarning( "Exception in Server::initConfiguration(): %s",
				  qPrintable( e.getMessage() )
				);
		qWarning( "Tries to reload default params." );

		try
		{
			// get default values

				// load params description from default config file 
			GParamsDescription paramsDescription;
			paramsDescription.loadFromFile( CAMERA_CONFIG_FILE );
				// get params with default values
			params = paramsDescription.defaultParams();
				// save it
			params.saveToFile( PARAMS_CONFIG_FILE );
		}
		catch( const GException & e )
		{
			// TODO handle error
			qWarning( "Exception in Server::initConfiguration(): %s",
					qPrintable( e.getMessage() )
					);
		}
	}
		// write to global data
	GlobalData::instance()->setParams( params );
		// load this params 
	readParams( GlobalData::instance()->getParams() );

	
		// create connections to dataBase
	createEventBaseConnection( EVENTS_DATABASE_FILENAME );

		// set frame data to GlobalData
	uint64_t timestamp;
	quint8   shrink = 0;
	GlobalData::instance()->setImage( readJpeg( &timestamp, shrink ) );
	GlobalData::instance()->setImageWidth( camera_image_width() );
	GlobalData::instance()->setImageHeight( camera_image_height() );
	
		// udp receiver
	connect( &_udpReceiver,
			 SIGNAL( packetsReceived( const QQueue< QPair<quint16, QByteArray> >& ) ),
			 this,
			 SLOT( udpPacketsReceived( const QQueue< QPair<quint16, QByteArray> >& ) ) );
	_udpReceiver.setCongestionEnabled( true );

}
// ---------------------------------------------------------------------------


// -------------------- EventServer::readParams() ----------------------------
void EventServer::readParams( const GParams & params )
{
	_jpegSendingInterval = params.value( PARAM_JPEG_INTERVAL ).toUInt();
	
	QString address = params.value( PARAM_CO_CAMERA_IP ).toString();
	_coCameraIP.setAddress( address );
		// restart receiving
	_udpReceiver.stopReceiving();
	_udpReceiver.startReceiving( UDP_RECEIVER_PORT );

	int jpegQuality = params.value( PARAM_JPEG_SIZE ).toInt();
	camera_jpeg_set_quality( jpegQuality );

	int exposure = params.value( PARAM_EXPOSURE ).toInt();
	camera_set_auto_exposure( 0 );
	camera_set_exposure( exposure );

	// NOTE is it really needed? binding with address should fixed if needed 
// 		// start receiving if address is valid
// 	if( !_coCameraIP.isNull() )
// 	{
// 		_udpReceiver.startReceiving( UDP_RECEIVER_PORT, _coCameraIP );
// 	}
// 	else
// 	{
// 		qWarning( "Null udpReceiver bind address.");
// 
// 		// TODO remove this later, just stop receiving
// 		_udpReceiver.startReceiving( UDP_RECEIVER_PORT );
// 	}
}
// ---------------------------------------------------------------------------


// ---------------------- EventServer::sendJpeg() ----------------------------
void EventServer::sendJpeg( const QByteArray & jpeg,
							const QHostAddress& address,
	   						quint16 port
						  )
{
	// create packet
	GAutoFrame packet;
	packet.setImage( jpeg );
	packet.setWidth( camera_image_width() ); // NOTE jpeg may have another size
	packet.setHeight( camera_image_height() );
	packet.setType( GAutoFrame::JPEG2K_YUV );
	packet.setId( _image_id ++ );
	
	// send packet
	QByteArray packetBuffer;
	QDataStream stream( &packetBuffer, QIODevice::WriteOnly );
	
	GHeader header = packet.header();
	
	stream << header << packet;
	
	_udpSender.sendSync( packetBuffer, address, port );
}
// ---------------------------------------------------------------------------


// ----------------------- EventServer::readJpeg() ---------------------------
QByteArray EventServer::readJpeg( uint64_t * timestamp, quint8 shrink )const
{
	camera_jpeg_set_shrink( shrink );
	
		// get jpeg
	const int bufferSize = 3000 * camera_get_jpeg_quality();
	QByteArray buffer;
	buffer.resize( bufferSize );
	
	size_t jpegSize = 0;
	if ( CAMERA_OK != camera_get_jpeg( (uint8_t*)buffer.data(),
		 							   buffer.size(),
									   &jpegSize,
									   timestamp
									 ) )
	{
// 		qWarning("Could not read jpeg: %s", camera_error_message() );
		return QByteArray();
	}
	buffer.resize( jpegSize );

	return buffer;
}
// ---------------------------------------------------------------------------


// ------------------ EventServer::addImageToBuffer() ------------------------
void EventServer::addImageToBuffer( const QByteArray & image,
									uint64_t timestamp
								  )
{
	BufferItem item;
	item.first  = timestamp;
	item.second = image;

	_imagesBuffer.prepend( item );
	
		// keep buffer size
	if( _imagesBuffer.size() > IMAGES_IN_BUFFER )
	{
		_imagesBuffer.removeLast();
	}
}
// ---------------------------------------------------------------------------


// ------------------- EventServer::udpPacketsReceived() ---------------------
void EventServer::udpPacketsReceived( const QQueue< QPair<quint16, QByteArray> >& packets )
{
		// search list from the end, process one packt of each type
	QList<quint16> processedTypes;
	
	for( int i = packets.size() - 1; i >= 0 ; i-- )
	{
		// read last packet data
		QByteArray data = packets[i].second;// tricky: get data from last packet
		
		QDataStream in( data );
		in.setVersion( QDATASTREAM_VERSION );
		
			// read header 
		GHeader header;
		header.read( in );
			// create apropriate packet
		GNetPacket * pPacket = GNetPacketFactory::instance()->createPacket( header );
			// read packet from stream 
		pPacket->read( in );
		
		// check packet type
		if ( ! processedTypes.contains( pPacket->packetType() ) )
		{
			// ok, new type!
			processPacket( pPacket );
			processedTypes.append( pPacket->packetType() );
		}
		
		delete pPacket;
	}
}
// ---------------------------------------------------------------------------


// -------------------- EventServer::processPacket() -------------------------
void EventServer::processPacket( GNetPacket * pPacket )
{
	if ( pPacket )
	{
		switch ( pPacket->packetType() )
		{
			case GAutoPacket::PACKET_EVENT_TRIGGER:
				process_PACKET_EVENT_TRIGGER( qobject_cast< GAutoEventTrigger * >( pPacket ) );
				break;

			// ignore others
			default:
				; // nothing
		}
	} // pPacket
}
// ---------------------------------------------------------------------------


// ------------- EventServer::process_PACKET_EVENT_TRIGGER() -----------------
void EventServer::process_PACKET_EVENT_TRIGGER( GAutoEventTrigger * pPacket )
{
		// check pointer 
	if( !pPacket ) return;

	quint16 delay = pPacket->sendTime() - pPacket->shutterTime();
	BufferItem firstImage = previousImage( delay );
	BufferItem secondImage = nextImage( delay );
	
// 	qDebug( "shutter = %lld   first = %lld   second = %lld",
// 			( pPacket->shutterTime() ),
// 			( firstImage.first ),
// 			( secondImage.first )
// 		  );
	qDebug() << "shutter" << pPacket->shutterTime()
			<< "first" << firstImage.first
			<< "second" << secondImage.first
			<< "delay" << delay;
	
		// insert into table only when image where found
	if( ( firstImage.first > 0 ) && ( secondImage.first > 0 ) )
	{
			// lock base 
		GlobalData::instance()->setBaseLocked();
		
		QSqlQuery query;
		query.prepare( "INSERT INTO autostop_events ( type, dateTime, "
				"message, firstImage, secondImage ) "
				"VALUES ( :type, :dateTime, :message,"
				" :firstImage, :secondImage )"
					);
		query.bindValue( ":type", (quint8)pPacket->eventType() );
		QDateTime eventDateTime = QDateTime::currentDateTime();
		query.bindValue( ":dateTime" , eventDateTime.toString( Qt::ISODate ) );
		query.bindValue( ":message", pPacket->message() );
		query.bindValue( ":firstImage", firstImage.second, QSql::In | QSql::Binary);
		query.bindValue( ":secondImage", secondImage.second, QSql::In | QSql::Binary);

		qDebug() << "inserting" << query.exec() << query.lastError().text();

			// clear query
		query.clear();

			// unlock base 
		GlobalData::instance()->setBaseUnlocked();
	}
}
// ---------------------------------------------------------------------------


// -------------------- EventServer::previousImage() -------------------------
BufferItem EventServer::previousImage( const quint16 delay )const
{
	quint64 timestamp = camera_get_time() - delay;
	
	BufferItem previousItem;
	previousItem.first  = 0;
	previousItem.second = QByteArray();
	BufferItem tmpItem;
	
		// search for previous image 
	bool found = false;
	QLinkedListIterator< BufferItem > i( _imagesBuffer );
 	while( i.hasNext() )
	{
		tmpItem = i.next();
		if( tmpItem.first < timestamp )
		{
			found = true;
			break;
		}
	}
	
	if( found )
	{
		previousItem = tmpItem;
	}
	
	return previousItem;
}
// ---------------------------------------------------------------------------


// -------------------- EventServer::nextImage() -----------------------------
BufferItem EventServer::nextImage( const quint16 delay )const
{
	quint64 timestamp = camera_get_time() - delay;
	
	BufferItem nextItem;
	nextItem.first  = 0;
	nextItem.second = QByteArray();
	BufferItem tmpItem;
	
		// search for previous image 
	bool found = false;
	QLinkedListIterator< BufferItem > i( _imagesBuffer );
	i.toBack();
	while( i.hasPrevious() )
	{
		tmpItem = i.previous();
		if( tmpItem.first > timestamp )
		{
			found = true;
			break;
		}
	}
	
	if( found )
	{
		nextItem = tmpItem;
	}
	
	return nextItem;
}
// ---------------------------------------------------------------------------






// eof
